/*
 * this is base for generated code.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <math.h>

#ifdef __GNUC__
#define LG_VAR(v)   v __attribute__((unused))
#else
#define LG_VAR(v)   v
#endif

typedef struct _lg_opt_info     lg_opt_info;
typedef struct _lg_arg_list     lg_arg_list;
typedef union _lg_arg           lg_arg;
typedef struct _lg_token        lg_token;
typedef struct _lg_error_info   lg_error_info;

typedef void (*lg_action_func)(lg_arg_list *);

enum LG_TAG {
  LG_E_OPT_TAG,
  LG_D_OPT_TAG,
  LG_F_OPT_TAG,
  LG_OUTPUT_OPT_TAG,
  LG_UINT_OPT_TAG,
  LG_FLOAT_OPT_TAG,
  LG_VARGS_OPT_TAG,
  LG_POS_ARGS_TAG,
  LG_MINUS1_TAG,
  LG_ASSOC_ARG_TAG,
  LG_ARG_TAG
};

#define LG_OPT_COUNT    LG_MINUS1_TAG

enum LG_ARG_TYPE {
  LG_STR_TYPE,
  LG_INT_TYPE,
  LG_UINT_TYPE,
  LG_FLOAT_TYPE
};

enum LG_ERROR_CODE {
  LG_RANGE_ERROR,
  LG_CONVERT_ERROR,
  LG_MULTIPLE_ERROR,
  LG_SELECT_ERROR,
  LG_USER_ERROR
};

struct _lg_opt_info {
  int tag;
  const char sopt;
  const char *lopt;
  const char *dep;
  int nargs;
  int ndefvals;
  int var_args;
  int arg_type;
  int multiple;
  lg_action_func action;
};

struct _lg_arg_list {
  int arg_type;
  int nargs;
  int nvargs;
  union _lg_arg *args;
  union _lg_arg *vargs;
};

union _lg_arg {
  const char *csval;
  char *sval;
  long lval;
  unsigned long ulval;
  double dval;
};

struct _lg_token {
  int tag;
  char *value;
  struct _lg_token *next;
};

struct _lg_error_info {
  int code;
  int tag;
  lg_arg_list *args;
  int index;
};

static void lg_parse(int, char **);
static void lg_init_default_values();
static int lg_scan_tokens(int, char **, lg_token *);
static int lg_scan_lopt_tokens(lg_token *, int *, char *);
static int lg_sopt_token(char);
static int lg_lopt_token(char *);
static int lg_opt_token(char, char *);
static void lg_set_token(lg_token *, int, int, const char *);
static int lg_max_nargs(int, lg_token *);
static void lg_validate_tokens(int, lg_token *);
static void lg_parse_tokens(int, lg_token *, int);
static void lg_invoke_action(int, int, char **);
static void lg_invoke_pos_action(int, char **);
static int lg_opt_nargs(int);
static void lg_convert_args(int, int, lg_arg_list *, char **);
static int lg_set_const(int, char *, lg_arg *);
static void lg_validate_selects(int, lg_arg *);
static void lg_free_args(lg_arg_list *);
static void lg_set_default_values(lg_opt_info *, lg_arg_list *);

static void lg_error(const char *, ...);
static void lg_raise(int, int, lg_arg_list *, int);
static void lg_handle_error();
static void lg_print_error_msg(int, lg_opt_info *, lg_arg_list *, int);
static void lg_print_cmd(const char *, ...);
static void lg_eprintf(const char *, ...);
static void lg_veprintf(const char *, va_list);

static void lg_print_usage_and_exit(int);

/* for options */
static void f_opt_action(lg_arg_list *);
static void o_opt_action(lg_arg_list *);
static void uint_opt_action(lg_arg_list *);
static void float_opt_action(lg_arg_list *);
static void vargs_opt_action(lg_arg_list *);
static void pos_action(lg_arg_list *);
static void minus1_action(lg_arg_list *);

static lg_arg lg_default_values[5][1];

static lg_opt_info lg_opt_table[] = {
  {0, 'e', NULL, "deprecation message", 0, 0, 0, 0, 0, NULL},
  {1, 'd', NULL, NULL, 0, 0, 0, 0, 0, NULL},
  {2, 'f', "file", NULL, 1, 0, 0, LG_INT_TYPE, 0, (lg_action_func)f_opt_action},
  {3, 'o', "output", NULL, 1, 1, 0, LG_STR_TYPE, 0, (lg_action_func)o_opt_action},
  {4, 0, "uint", NULL, 1, 0, 0, LG_UINT_TYPE, 1, (lg_action_func)uint_opt_action},
  {5, 0, "float", NULL, 1, 0, 0, LG_FLOAT_TYPE, 1, (lg_action_func)float_opt_action},
  {6, 0, "vargs", NULL, 0, 0, 1, LG_STR_TYPE, 0, (lg_action_func)vargs_opt_action},
  {7, 0, NULL, NULL, 1, 0, 0, LG_STR_TYPE, 0, (lg_action_func)pos_action},
  {8, 0, NULL, NULL, 0, 0, 0, 0, 0, (lg_action_func)minus1_action},
  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};

static char *lg_cmd = NULL;
static lg_error_info *lg_handler = NULL;


static void
lg_init_default_values()
{
  static int inited = 0;

  if (inited)
    return;

  lg_default_values[3][0].csval = "default_path";
  inited = 1;
}

static void
lg_parse(int argc, char **argv)
{
  int i, tok_size, max_nargs = 0;
  lg_token *tokens;

  lg_init_default_values();
  lg_cmd = (char *)malloc(strlen(argv[0])+1);
  strcpy(lg_cmd, argv[0]);
  --argc;
  ++argv;

  tok_size = lg_scan_tokens(argc, argv, NULL);
  printf("token size = %d, argc = %d\n", tok_size, argc);
  tokens = (lg_token *)malloc(sizeof(lg_token) * tok_size);
  lg_scan_tokens(argc, argv, tokens);
  lg_validate_tokens(tok_size, tokens);
  for (i = 0; i < tok_size; i++) {
    printf("token value = %s\n", tokens[i].value);
  }

  max_nargs = lg_max_nargs(tok_size, tokens);
  printf("max arg size = %d\n", max_nargs);
  lg_parse_tokens(tok_size, tokens, max_nargs);
}

static int
lg_scan_tokens(int argc, char **argv, lg_token *tokens)
{
  int tok_size = 0, tag = -1, scan_opts = 1;
  char *opt;

  while (argc > 0) {
    if (scan_opts) {
      /* "--" */
      if (strcmp(*argv, "--") == 0) {
        scan_opts = 0;

      /* "-" */
      } else if (strcmp(*argv, "-") == 0) {
        if (tokens != NULL)
          lg_set_token(tokens, tok_size, LG_MINUS1_TAG, "-");
        tok_size++;

      /* long option */
      } else if (strstr(*argv, "--") == *argv) {
        opt = *argv + 2;
        if (!lg_scan_lopt_tokens(tokens, &tok_size, opt)) {
          lg_print_cmd("invalid option: --%s\n", opt);
          lg_print_usage_and_exit(1);
        }

      /* short options or long option */
      } else if (**argv == '-') {
        opt = *argv + 1;

        /* negative integer */
        if (isdigit(*opt)) {
          if (tokens != NULL)
            lg_set_token(tokens, tok_size, LG_ARG_TAG, *argv);
          tok_size++;
        
        } else if (!lg_scan_lopt_tokens(tokens, &tok_size, opt)) {
          while (*opt) {
            if ((tag = lg_sopt_token(*opt)) > -1) {
              if (tokens != NULL)
                lg_set_token(tokens, tok_size, tag, opt);
              tok_size++;
              opt++;

              /* argument */
              if (lg_opt_nargs(tag) > 0 && *opt) {
                if (tokens != NULL)
                  lg_set_token(tokens, tok_size, LG_ARG_TAG, opt);
                tok_size++;
                break;
              }
            } else {
              lg_print_cmd("invalid option: -%c\n", *opt);
              lg_print_usage_and_exit(1);
            }
          }
        }

      /* argument */
      } else {
        if (tokens != NULL)
          lg_set_token(tokens, tok_size, LG_ARG_TAG, *argv);
        tok_size++;
      }

    /* argument */
    } else {
      if (tokens != NULL)
        lg_set_token(tokens, tok_size, LG_ARG_TAG, *argv);
      tok_size++;
    }

    argc--;
    argv++;
  }

  return tok_size;
}

static int
lg_sopt_token(char c)
{
  return lg_opt_token(c, NULL);
}

static int
lg_scan_lopt_tokens(lg_token *tokens, int *sizep, char *arg)
{
  char *opt, *value;
  int tag;

  tag = lg_lopt_token(arg);
  if (tag == -1)
    return 0;
  (*sizep)++;

  if ((value = strchr(arg, '=')) != NULL) {
    if (tokens != NULL) {
      opt = (char *)malloc(value - arg + 1);
      strncpy(opt, arg, value - arg);
      opt[value - arg] = 0;
      lg_set_token(tokens, *sizep-1, tag, opt);
      lg_set_token(tokens, *sizep, LG_ASSOC_ARG_TAG, value+1);
      free(opt);
    }
    (*sizep)++;
  } else if (tokens != NULL)
    lg_set_token(tokens, *sizep-1, tag, arg);

  return 1;
}

static int
lg_lopt_token(char *s)
{
  return lg_opt_token(0, s);
}

static int
lg_opt_token(char sopt, char *lopt)
{
  int i = 0;
  char *eq;
  lg_opt_info *table = lg_opt_table;

  while (table->sopt != 0 || table->lopt != NULL) {
    if (table->sopt && sopt) {
      if (table->sopt == sopt)
        return i;
    } else if (table->lopt && lopt) {
      if ((eq = strchr(lopt, '=')) != NULL) {
        if (lopt + strlen(table->lopt) == eq &&
            strstr(lopt, table->lopt) == lopt)
          return i;
      } else if (strcmp(lopt, table->lopt) == 0)
        return i;
    }
    table++;
    i++;
  }

  return -1;
}

static void
lg_set_token(lg_token *tokens, int tok_size, int tag, const char *value)
{
  tokens[tok_size].tag = tag;
  tokens[tok_size].value = (char *)malloc(strlen(value)+1);
  strcpy(tokens[tok_size].value, value);
}

static int
lg_max_nargs(int tok_size, lg_token *tokens)
{
  int i, max = 0, size = 0;

  for (i = 0; i < tok_size; i++) {
    if (tokens[i].tag == LG_ASSOC_ARG_TAG) {
      if (max == 0)
        max = 1;
      size = 0;
    } else if (tokens[i].tag == LG_ARG_TAG) {
      size++;
      if (size > max)
        max = size;
    } else
      size = 0;
  }
  if (size > max)
    max = size;

  return max;
}

static void
lg_validate_tokens(int tok_size, lg_token *tokens)
{
  int multiples[LG_OPT_COUNT], i, tag;

  for (i = 0; i < LG_OPT_COUNT; i++)
    multiples[i] = 0;

  for (i = 0; i < tok_size; i++) {
    tag = tokens[i].tag;
    if (tag != LG_ARG_TAG) {
      if (multiples[tag] && !lg_opt_table[tag].multiple) {
        lg_raise(LG_MULTIPLE_ERROR, tag, NULL, 0);
        break;
      } else
        multiples[tokens[i].tag] = 1;
    }
  }

  lg_handle_error();
}

static void
lg_parse_tokens(int tok_size, lg_token *tokens, int max)
{
  int i, argc, tag = -1;
  lg_token t;
  lg_opt_info *opt = NULL;
  char **args;

  args = (char **)malloc(sizeof(char *) * max);
  argc = 0;
  for (i = 0; i < tok_size; i++) {
    t = tokens[i];

    printf("token %d\n", t.tag);
    /* argument */
    if (t.tag == LG_ARG_TAG || t.tag == LG_ASSOC_ARG_TAG) {
      /* option argument */
      if (tag > -1) {
        args[argc++] = t.value;
        if (!opt->var_args && argc >= opt->nargs) {
          lg_invoke_action(tag, argc, args);
          tag = -1;
          argc = 0;
        }

      /* positional argument */
      } else {
        args[argc++] = t.value;
      }

    /* begin option */
    } else {
      if (tag > -1) {
        lg_invoke_action(tag, argc, args);
        argc = 0;
      }

      if (!lg_opt_table[t.tag].var_args && lg_opt_nargs(t.tag) == 0) {
        lg_invoke_action(t.tag, 0, NULL);
        tag = -1;
      } else {
        tag = t.tag;
        opt = &lg_opt_table[t.tag];
      }
    }
  }

  if (tag > -1) {
    lg_invoke_action(tag, argc, args);
    tag = -1;
    argc = 0;
  }

  if (argc >= lg_opt_table[LG_POS_ARGS_TAG].nargs)
    lg_invoke_pos_action(argc, args);
  else
    lg_print_usage_and_exit(1);
}

static int
lg_opt_nargs(int tag)
{
  return lg_opt_table[tag].nargs;
}

static void
lg_invoke_action(int tag, int nargs, char **args)
{
  lg_opt_info *opt;
  lg_arg_list *cargs;

  opt = &lg_opt_table[tag];
  printf("invoke %d -> %s %d\n", tag, opt->lopt, opt->arg_type);

  /* check number of arguments */
  if (!opt->var_args && ((opt->nargs - opt->ndefvals) > nargs))
    lg_print_usage_and_exit(1);

  /* print deprecation message */
  if (opt->dep != NULL)
    lg_print_cmd("%s\n", opt->dep);

  cargs = (lg_arg_list *)malloc(sizeof(lg_arg_list));
  cargs->arg_type = opt->arg_type;
  cargs->nargs = nargs;
  cargs->args = (lg_arg *)malloc(sizeof(lg_arg) * nargs);

  lg_convert_args(tag, nargs, cargs, args);
  lg_handle_error();

  printf("needs %d, given %d\n", opt->nargs, nargs);
  if (opt->nargs > nargs)
    lg_set_default_values(opt, cargs);

  /* variable arguments */
  if (opt->var_args) {
    cargs->nvargs = nargs - opt->nargs;
    cargs->vargs = cargs->args + opt->nargs;
  }

  if (opt->action != NULL) {
    opt->action(cargs);
  }

  lg_free_args(cargs);
}

static void
lg_invoke_pos_action(int nargs, char **args)
{
  lg_invoke_action(LG_POS_ARGS_TAG, nargs, args);
}

static void
lg_convert_args(int tag, int nargs, lg_arg_list *cargs, char **args)
{
  int i;
  char *arg;
  char *earg = NULL;
  lg_arg *carg;

  for (i = 0; i < nargs; i++) {
    arg = args[i];
    carg = &cargs->args[i];
    errno = 0;
    if (lg_set_const(tag, arg, carg)) {
      lg_validate_selects(tag, carg);
      continue;
    }

    switch (cargs->arg_type) {
    case LG_STR_TYPE:
      carg->sval = (char *)malloc(strlen(arg) + 1);
      strcpy(carg->sval, arg);
      break;

    case LG_INT_TYPE:
      carg->lval = strtol(arg, &earg, 0);
      if (errno == ERANGE) {
        if (carg->lval == LONG_MAX || carg->lval == LONG_MIN) {
          lg_raise(LG_RANGE_ERROR, tag, cargs, i);
          return;
        }
        else
          lg_error("lg_convert_args(): unknown errno %d", errno);
      } else if (*earg != 0) {
        lg_raise(LG_CONVERT_ERROR, tag, cargs, i);
        return;
      }
      break;

    case LG_UINT_TYPE:
      carg->ulval = strtoul(arg, &earg, 0);
      if (errno == ERANGE) {
        if (carg->ulval == ULONG_MAX) {
          lg_raise(LG_RANGE_ERROR, tag, cargs, i);
          return;
        }
        else
          lg_error("lg_convert_args(): unknown errno %d", errno);
      } else if (*earg != 0) {
        lg_raise(LG_CONVERT_ERROR, tag, cargs, i);
        return;
      }
      break;

    case LG_FLOAT_TYPE:
      carg->dval = strtod(arg, &earg);
      if (errno == ERANGE) {
        if ((!(HUGE_VAL < carg->dval) && !(carg->dval < HUGE_VAL)) ||
            (!(-HUGE_VAL < carg->dval) && !(carg->dval < -HUGE_VAL)) ||
            (!(0 < carg->dval) && !(carg->dval < 0))) {
          lg_raise(LG_RANGE_ERROR, tag, cargs, i);
          return;
        }
        else
          lg_error("lg_convert_args(): unknown errno %d", errno);
      } else if (*earg != 0) {
        lg_raise(LG_CONVERT_ERROR, tag, cargs, i);
        return;
      }
      break;

    default:
      lg_error("lg_convert_args(): unknown argument type -- %d",
               cargs->arg_type);
    }
    lg_validate_selects(tag, carg);
  }
}

/* auto generated */
static int
lg_set_const(int tag, char *name, lg_arg *arg)
{
  if (tag == LG_UINT_OPT_TAG) {
    if (strcmp(name, "zero") == 0) {
      arg->ulval = 0;
      return 1;
    }
  }
  return 0;
}

/* auto generated */
static void
lg_validate_selects(LG_VAR(int tag), LG_VAR(lg_arg *arg))
{
  if (tag == LG_F_OPT_TAG) {
    if (arg->lval != 0 && arg->lval != 1)
      lg_raise(LG_SELECT_ERROR, tag, NULL, 0);
  }
}

static void
lg_set_default_values(lg_opt_info *opt, lg_arg_list *args)
{
  int i;
  const char *csval;

  for (i = args->nargs; i < opt->nargs; i++) {
    switch (opt->arg_type) {
    case LG_STR_TYPE:
      csval = lg_default_values[opt->tag][i].sval;
      args->args[i].sval = (char *)malloc(strlen(csval)+1);
      strcpy(args->args[i].sval, csval);
      break;
    case LG_INT_TYPE:
      args->args[i].lval = lg_default_values[opt->tag][i].lval;
      break;
    case LG_UINT_TYPE:
      args->args[i].ulval = lg_default_values[opt->tag][i].ulval;
      break;
    case LG_FLOAT_TYPE:
      args->args[i].dval = lg_default_values[opt->tag][i].dval;
      break;
    default:
      lg_error("lg_set_default_value(): unknown argument type");
      exit(1);
    }
  }
}

static void
lg_free_args(lg_arg_list *args)
{
  int i;
  lg_arg *arg;

  for (i = 0; i < args->nargs; i++) {
    arg = &args->args[i];
    switch (args->arg_type) {
    case LG_STR_TYPE:
      free(arg->sval);
      break;

    case LG_INT_TYPE: case LG_UINT_TYPE: case LG_FLOAT_TYPE:
      /* do nothing */
      break;

    default:
      lg_error("lg_free_args(): unknown argument type -- %d",
               args->arg_type);
    }
  }
  free(args);
}

static void
lg_error(const char *format, ...)
{
  va_list list;

  lg_print_cmd(NULL);
  va_start(list, format);
  lg_veprintf(format, list);
  lg_eprintf("\n");
  va_end(list);
  exit(EXIT_FAILURE);
}

static void
lg_raise(int code, int tag, lg_arg_list *args, int index)
{
  lg_handler = (lg_error_info *)malloc(sizeof(lg_error_info));
  lg_handler->code = code;
  lg_handler->tag = tag;
  lg_handler->args = args;
  lg_handler->index = index;
}

static void
lg_handle_error()
{
  lg_opt_info *opt;

  if (lg_handler != NULL) {
    lg_print_cmd(NULL);
    opt = &lg_opt_table[lg_handler->tag];
    if (opt->sopt && opt->lopt)
      lg_eprintf("-%c, --%s: ", opt->sopt, opt->lopt);
    else if (opt->sopt)
      lg_eprintf("-%c: ", opt->sopt);
    else if (opt->lopt)
      lg_eprintf("--%s: ", opt->lopt);
    else
      lg_eprintf("unknown option (%d): ", lg_handler->tag);
    lg_print_error_msg(lg_handler->code, opt, lg_handler->args, lg_handler->index);
    lg_eprintf("\n");
    exit(EXIT_FAILURE);
  }
}

static void
lg_print_error_msg(int code, LG_VAR(lg_opt_info *opt), lg_arg_list *args, int i)
{
  switch (code) {
  case LG_RANGE_ERROR:
    if (args->nargs > 1)
      lg_eprintf("value of argument %d is out of range", i);
    else
      lg_eprintf("value is out of range");
    break;

  case LG_CONVERT_ERROR:
    if (args->nargs > 1)
      lg_eprintf("value of argument %d is invalid", i);
    else
      lg_eprintf("value is invalid");
    break;

  case LG_MULTIPLE_ERROR:
    lg_eprintf("cannot be used more than once");
    break;

  case LG_SELECT_ERROR:
    lg_eprintf("the selection value is invalid");
    break;

  default:
    lg_eprintf("unknown error");
  }
}

static void
lg_print_cmd(const char *format, ...)
{
  va_list list;

  lg_eprintf("%s: ", lg_cmd);
  if (format != NULL) {
    va_start(list, format);
    lg_veprintf(format, list);
    va_end(list);
  }
}

static void
lg_eprintf(const char *format, ...)
{
  va_list list;

  va_start(list, format);
  lg_veprintf(format, list);
  va_end(list);
}

static void
lg_veprintf(const char *format, va_list list)
{
  vfprintf(stderr, format, list);
}

static void
lg_print_usage_and_exit(int code)
{
  printf("usage: ...\n");
  exit(code);
}

static void
f_opt_action(lg_arg_list *args)
{
  printf("option arg = %ld\n", args->args[0].lval);
}

static void
o_opt_action(lg_arg_list *args)
{
  printf("-o arg = %s\n", args->args[0].sval);
}

static void
uint_opt_action(lg_arg_list *args)
{
  printf("uint arg = %ld\n", args->args[0].ulval);
}

static void
float_opt_action(lg_arg_list *args)
{
  printf("float arg = %f\n", args->args[0].dval);
}

static void
vargs_opt_action(lg_arg_list *args)
{
  int i;
  printf("nvargs = %d\n", args->nvargs);
  for (i = 0; i < args->nvargs; i++)
    printf("  vargs = %s\n", args->vargs[i].sval);
}

static void
pos_action(lg_arg_list *args)
{
  printf("pos = %s\n", args->args[0].sval);
  lg_error("error test %s", "[OK]");
}

static void
minus1_action(LG_VAR(lg_arg_list *args))
{
  printf("- option\n");
}

int main(int argc, char **argv)
{
  lg_parse(argc, argv);
  return 0;
}
